﻿using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace DotNet.Common
{
    public class FiniteTable<FiniteState, FiniteOperation>
        where FiniteState : IConvertible, IComparable
        where FiniteOperation : IConvertible
    {
        private FiniteState _state;
        private Dictionary<FiniteOperation, FiniteState> _currentOperationTable;
        public delegate void OperationDelegate(FiniteState currentState, FiniteOperation currentOperation);

        public event VoidInvoker EntryAction;
        public event VoidInvoker ExitAction;
        public event OperationDelegate OperationAction;

        public FiniteState State
        {
            get { return _state; }
        }

        public FiniteTable(FiniteState state)
        {
            Debug.Assert(typeof(FiniteState).IsEnum && typeof(FiniteOperation).IsEnum);

            _state = state;
            _currentOperationTable = new Dictionary<FiniteOperation, FiniteState>();
        }

        public void Add(FiniteOperation operation, FiniteState state)
        {
            _currentOperationTable.Add(operation, state);
        }

        public FiniteState Run(FiniteOperation operation)
        {
            if (OperationAction != null)
            {
                OperationAction(_state, operation);
            }
            if (_currentOperationTable.ContainsKey(operation))
            {
                return _currentOperationTable[operation];
            }
            else
            {
                return _state;
            }
        }

        public void Entry()
        {
            if (EntryAction != null)
            {
                EntryAction();
            }
        }

        public void Exit()
        {
            if (ExitAction != null)
            {
                ExitAction();
            }
        }
    }

    public class FiniteStateMachine<FiniteState, FiniteOperation>
        where FiniteState : IConvertible, IComparable
        where FiniteOperation : IConvertible
    {
        private Dictionary<FiniteState, FiniteTable<FiniteState, FiniteOperation>> _fsmDictionary;
        private FiniteTable<FiniteState, FiniteOperation> _currentTable;

        public FiniteState CurrentState
        {
            get
            {
                return _currentTable.State;
            }
        }

        private FiniteTable<FiniteState, FiniteOperation> CurrentTable
        {
            set
            {
                if (_currentTable != null)
                {
                    _currentTable.Exit();
                }
                _currentTable = value;
                if (value != null)
                {
                    value.Entry();
                }
            }
        }

        public FiniteStateMachine(FiniteState initState, FiniteTable<FiniteState, FiniteOperation>[] fsmTables)
        {
            Debug.Assert(fsmTables != null, "FiniteStateMachine Tables CANNOT be NULL!");

            _fsmDictionary = new Dictionary<FiniteState, FiniteTable<FiniteState, FiniteOperation>>();
            foreach (FiniteTable<FiniteState, FiniteOperation> table in fsmTables)
            {
                _fsmDictionary.Add(table.State, table);
                if (table.State.CompareTo(initState) == 0)
                {
                    this.CurrentTable = table;
                }
            }

            Debug.Assert(_currentTable != null, "FiniteStateMachine Tables CANNOT Contain The Initial FiniteState!");
        }

        public void RunOperation(FiniteOperation operation)
        {
            FiniteState nextState = _currentTable.Run(operation);
            if (nextState.CompareTo(this.CurrentState) != 0)
            {
                Debug.Assert(_fsmDictionary.ContainsKey(nextState));
                this.CurrentTable = _fsmDictionary[nextState];
            }
        }
    }
}

#if false
void Run()
{
    FiniteTable<LoginState, LoginOperation>[] table = new FiniteTable<LoginState, LoginOperation>[2];
    table[0] = new FiniteTable<LoginState, LoginOperation>(LoginState.Opened);
    table[1] = new FiniteTable<LoginState, LoginOperation>(LoginState.Closed);
    table[0].Add(LoginOperation.Pull, LoginState.Closed);
    table[1].Add(LoginOperation.Push, LoginState.Opened);

    table[0].EntryAction += delegate { Debug.WriteLine("Enter Opened"); };
    table[0].ExitAction += delegate { Debug.WriteLine("Exit Opened"); };
    table[0].OperationAction += delegate(LoginState state, LoginOperation operation) { Debug.WriteLine(state + ":" + operation); };
    table[1].EntryAction += delegate { Debug.WriteLine("Enter Closed"); };
    table[1].ExitAction += delegate { Debug.WriteLine("Exit Closed"); };
    table[1].OperationAction += delegate(LoginState state, LoginOperation operation) { Debug.WriteLine(state + ":" + operation); };

    FiniteStateMachine<LoginState, LoginOperation> loginMachine = new FiniteStateMachine<LoginState, LoginOperation>(LoginState.Opened, table);

    loginMachine.RunOperation(LoginOperation.Push);
    loginMachine.RunOperation(LoginOperation.Pull);
    loginMachine.RunOperation(LoginOperation.Pull);
    loginMachine.RunOperation(LoginOperation.Pull);
    loginMachine.RunOperation(LoginOperation.Push);

    Debug.WriteLine(loginMachine.CurrentState);
}
#endif
